<?php

require_once 'UE/Cache/Abstract.php';

/**
 * 缓存处理管理类
 * 
 * 完整的缓存单元的数据结构
 * array(
 *     'time' => time(),    // 缓存写入时的时间戳
 *     'expire' => $expire, // 缓存过期时间
 *     'valid' => true,     // 缓存是否有效
 *     'data' => $value     // 缓存的值
 * );
 * 
 * ============================================================================
 * 版权所有: (c) 2009-2011，<UEShop Inc>，并保留所有权利。
 * 网站地址: http://www.ueshop.cn
 * ----------------------------------------------------------------------------
 * 这不是一个自由软件！您只能在不用于商业目的的前提下对程序代码进行修改和使用；
 * 不允许对程序代码以任何形式任何目的的再发布。
 * ============================================================================
 * @author cmpan
 * @copyright   Copyright (c) 2009-2011 UEShop Inc. (http://www.ueshop.cn)
 * @license     http://www.ueshop.cn/1_0.txt
 */
final class UE_Cache_File extends UE_Cache_Abstract {
	
	private $_cachesDir = 'data/cache';

	private function _hashKey($key) {
		$key = md5($key);
		if(strlen($key) < 2) {
			$key = $key . '_';
		}
		
		return 	"{$key[0]}{$key[1]}/{$key}";
	}
	
	public function __construct() {
		if (defined('CACHE_DIR')) {
			$this->_setCacheDir(CACHE_DIR);
		}
	}
	
	/**
	 * 设置文件缓存目录
	 * @param string $dir
	 * @return UE_Cache
	 */
	protected function _setCacheDir($dir) {
		$this->_cachesDir = rtrim(str_replace('\\', '/', trim($dir)), '/');
		clearstatcache();
		if(!is_dir($this->_cachesDir)) {
			mkdir($this->_cachesDir, 0755, true);
		}
		//
		return $this;
	}
	
	/**
	 * 获取缓存文件
	 *
	 * @param string $key
	 * @return string
	 */
	protected function _getCacheFile($key) {
		return $this->_cachesDir . '/' . ltrim($key, '/') . '.php';
	}


	/**
	 * 设置缓存
	 *
	 * @param string $key
	 * @param mixed $value
	 * @param int $expire  如果要设置不删除缓存，请设置一个大点的整数
	 * @return UE_Cache
	 */
	public function set($key, $value, $expire = 0) {
		if (!$expire) {
			$expire = $this->_expire;
		}
		
		$this->_checkLock($key);
		
		$data = array('time' => time(), 'expire' => $expire, 'valid' => true, 'data' => $value);
		$this->_lock($key);
				
		try {
			$this->_store($key, $data);
			$this->_unlock($key);
		} catch (UE_Cache_Exception $e) {
			$this->_unlock($key);
			throw $e;
		}

		return $this;
	}

	/**
	 * 读取缓存
	 *
	 * @param string $key
	 * @param bool $checkExpired 是否检查过期情况
	 * @return mixed
	 */
	public function get($key, $checkExpired = true) {
		$data = $this->_fetch($key);
		if ($data && (!$checkExpired || ($data['valid'] && !$data['isExpired']))) {
			return $data['data'];
		}
		
		return false;
	}

	/**
	 * 读（包括过期无效的）缓存，取得完整的存贮结构(包括缓存各属性)
	 *
	 * @param string $key
	 * @return mixed
	 */
	private function _fetch($key)	{
		$this->_checkLock($key);

		$cacheFile = self::_getCacheFile($key);
        if (is_file($cacheFile) && is_readable($cacheFile)) {
			$data = include $cacheFile;
			
			if ($data) {
				$data['isExpired'] = ($data['expire'] && (time() - $data['time']) > $data['expire']) ? true : false;
				return $data;
			}
        }
        
		return false;
	}

	/**
	 * 删除缓存
	 *
	 * @param string $key
	 * @return UE_Cache
	 */
	public function delete($key) {
	    if(empty($key)) {
		    return false;
		}
		
		$file = self::_getCacheFile($key);
		if(is_file($file)) {
			$this->_checkLock($key)->_lock($key);
		    @unlink($file);
		    $this->_unlock($key);
		}
		
		return $this;		
	}
	
	/**
	 * 清空所有缓存
	 *
	 * @return UE_Cache
	 */
	public function clear($dir = '') {
		$dir = rtrim($this->_cachesDir, '/') . '/'.$dir;
		is_dir($dir) && clearDir($dir);
		
		return $this;
	}

	/**
	 * 把缓存设为无效
	 *
	 * @param string $key
	 * @return UE_Cache
	 */
	public function setInvalidate($key) {	
		$data = $this->_fetch($key);	
		try {			
			if ($data) {
				$this->_checkLock($key)->_lock($key);
				$data['valid'] = false;
				$this->_store($key, $data);
				$this->_unlock($key);
			}			
		} catch (UE_Cache_Exception $e) {
			$this->_unlock($key);
			throw $e;
		}

		return $this;
	}

	/**
	 * 缓存单元是否已经锁定
	 *
	 * @param string $key
	 * @return bool
	 */
	public function _isLocked($key) {
		$cacheFile = self::_getCacheFile($key);
		clearstatcache();
		return is_file($cacheFile . '.lock');
	}

	/**
	 * 锁定
	 *
	 * @param string $key
	 * @return UE_Cache
	 */
	public function _lock($key) {
		$cacheFile = self::_getCacheFile($key);
		$cacheDir  = dirname($cacheFile);
		if(!is_dir($cacheDir)) {
		    if(!@mkdir($cacheDir, 0755, true)) {
			    if(!is_dir($cacheDir)) {
			        throw new UE_Cache_Exception("Could not make cache directory");
			    }
		    }
		}

		// 设定缓存锁文件的访问和修改时间
		@touch($cacheFile . '.lock');
		return $this;
	}
  
	/**
	 * 解锁
	 *
	 * @param string $key
	 * @return UE_Cache
	 */
	public function _unlock($key) {
		$cacheFile = self::_getCacheFile($key);
	    @unlink($cacheFile . '.lock');
		return $this;
	}

	
	/**
	 * 缓存变量
	 * 为防止信息泄露，缓存文件格式为php文件，并以"<?php exit;?>"开头
	 *
	 * @param string $key 缓存变量下标
	 * @param string $value 缓存变量的值
	 * @return bool
	 */
    public function _store($key, $value) {
		$cacheFile = self::_getCacheFile($key);
		$cacheDir  = dirname($cacheFile);

		if(!is_dir($cacheDir)) {
		    if(!@mkdir($cacheDir, 0755, true)) {
			    throw new UE_Cache_Exception("Could not make cache directory");
		    }
		}
		
		$txt = "<?php\r\n"
		     . "/**\r\n"
		     . " * UE cache file, please don't edit me!\r\n"
             . " * Created: " . date('Y-m-d H:i:s') . " (" . microtime(1) . ")\r\n"
             . " * Cache: {$cacheFile}\r\n"
             . " */\r\n"
             . "defined('UE_IN') || die('Access Denied');\nreturn " . var_export($value, true) . ';';
        return @file_put_contents($cacheFile, $txt);
	}

}

